package com.yhb.channel.impl.ANYPAY;

import com.yhb.channel.ChannelConstant;
import com.yhb.channel.ChannelFactory;
import com.yhb.channel.PayChannel;
import com.yhb.channel.dao.ANYPAY.TX.AnyPayChannelBoundRelationshipTXMapper;
import com.yhb.channel.dao.ANYPAY.TX.AnyPayChannelBoundRelationshipTXRecord;
import com.yhb.channel.dao.flow.ExecutionFlow;
import com.yhb.channel.exception.*;
import com.yhb.channel.impl.ANYPAY.http.AnyPayHttpClient;
import com.yhb.channel.impl.ANYPAY.http.response.*;
import com.yhb.channel.impl.ANYPAY.http.utils.AnyPayRandomUtil;
import com.yhb.channel.model.*;
import com.yhb.channel.provider.ChannelBaseService;
import com.yhb.channel.provider.INotifyProvider;
import com.yhb.channel.utils.ChannelJSONUtil;
import com.yhb.channel.utils.ChannelLogger;
import com.yhb.channel.utils.ChannelStringUtils;
import lombok.extern.log4j.Log4j;
import lombok.var;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.util.TextUtils;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Log4j
@Service
public class AnyPayTXChannel implements PayChannel {

    // 配置
    @Value("${config.url}")
    private String baseUrl;

    // 工具
    @Autowired
    private ChannelBaseService channelBaseService;

    @Autowired
    private AnyPayHttpClient httpClient;

    @Autowired
    private AnyPayChannelBoundRelationshipTXMapper db;

    private ChannelLogger logger;


    /* ------------常量------------ */
    public static final String CHANNEL_NAME = "深圳安派";
    public static final String CHANNEL_TYPE = ChannelFactory.ANYPAY_TX;
    public static final String PRODUCT_CATEGORY = "TX";
    public static final String BINDCARD_SMS_PREFIX = CHANNEL_TYPE + "_BINDCARD_";


    /* -----------启动方法---------- */
    @PostConstruct
    private void init() {
        logger = new ChannelLogger(CHANNEL_NAME + PRODUCT_CATEGORY, AnyPayException.class, channelBaseService.iUtilProvider);
    }

    @NotNull
    public String getRequestId() {
        return AnyPayRandomUtil.generateNumber(32);
    }

    public String getChannelUserId(String userId) {
        var record = db.getBillingCardRecordByUserId(userId);
        return record.getChannelUserId();
    }

    /**
     * 检查是否签约过
     * @param userId 用户
     * @return 是否签约过
     */
    private boolean checkIfRegistered(String userId) {
        String cardId = db.selectBillingCardIdByUserId(userId);
        return !TextUtils.isEmpty(cardId);
    }

    public void logParams(String methodName, String... params) {
        logger.info("AnyPayChannel::" + methodName + "->params=>" + StringUtils.join(params, "|"));
    }

    @Override
    public void userSignUp(String userId, String province, String city, String area,
                           @NotNull IBankCard card, String payRate) {
        IUserIdentify userIdentify = channelBaseService.iUserIdentifyProvider.getUserIdentify(userId);
        var merchAddress = "广东省深圳市春南路2333号糖薰公寓4栋102号";
        var idCardNo = userIdentify.getIdCardNum();
        var name = userIdentify.getUserName();
        var mobile = card.getPhoneNumber();
        var bankName = card.getBankName();
        var idCardCode = "01";

        try {
            var requestId = getRequestId();
            var SERVICE_CODE = "01";
            Map<String, Object> data = new TreeMap<>();
            data.put("name", name);
            data.put("cardId", idCardNo); // 身份证号
            data.put("mobile", mobile);
            data.put("province", AnyPayConstant.getProvinceCode(province));
            data.put("city", AnyPayConstant.getCityCode(city));
            data.put("area", AnyPayConstant.getAreaCode(city));
            data.put("merAddress", merchAddress);
            data.put("certifTp", idCardCode); // 证件类型：身份证
            data.put("accountId", card.getNumber()); // 结算卡号
            data.put("cnapsNo", "000000000000"); // 结算卡银行支行代码
            data.put("t1consFee", "100"); // 提现手续费
            data.put("t1consRate", payRate); // 消费手续费率
            data.put("bankName", bankName);
            data.put("bankCode", AnyPayConstant.getBankCode(bankName));
            data.put("productCategory", PRODUCT_CATEGORY); // 套现
            data.put("bankProvince", AnyPayConstant.getProvinceCode(card.getProvince()));
            data.put("bankCity",  AnyPayConstant.getCityCode(card.getCity()));

            AnyPayBaseRespModel res = httpClient.http("userSignUp", requestId, data, SERVICE_CODE);
            var json = ChannelJSONUtil.toJson(res.getData());
            AnyPayUserSignUpResponse response = ChannelJSONUtil.parseJson(json, AnyPayUserSignUpResponse.class);
            String channelUserId = response.userId;
            AnyPayChannelBoundRelationshipTXRecord record = AnyPayChannelBoundRelationshipTXRecord.builder()
                    .agreementNo("") // 只注册未绑定
                    .bound(0)
                    .cardId(card.getId())
                    .cardType(card.getCardType())
                    .isBillingCard(1)
                    .channelUserId(channelUserId)
                    .userId(userId)
                    .build();
            if (db.getBillingCardRecordByUserId(userId) != null) {
                // 有则改之
                db.updateByUserId(record);
            } else {
                // 无则增加
                db.insert(record);
            }
            logger.info("userSignUp success");
        } catch (Exception e) {
            logger.fail(AnyPayConstant.ERROR_DEFAULT_CODE, "入网失败(userSignUp fail):\n"
                    + e.getMessage(), e);
        }
    }

    @Override
    public void updatePayRate(String userId, String payRate) {
        try {
            var requestId = getRequestId();
            var UPDATE_PAY_RATE_SERVICE_CODE = "15";
            var channelUserId = getChannelUserId(userId);
            Map<String, Object> data = new TreeMap<>();
            data.put("userId", channelUserId);
            data.put("productCategory", PRODUCT_CATEGORY);
            data.put("t1consFee", "100");
            data.put("t1consRate", payRate);
            httpClient.http("updatePayRate",
                    requestId, data, UPDATE_PAY_RATE_SERVICE_CODE);
            logger.info("updatePayRate success");
        } catch (Exception e) {
            logger.fail(AnyPayConstant.ERROR_DEFAULT_CODE,
                    "修改费率失败(updatePayRate fail):\n" + e.getMessage(), e);
        }
    }

    @Override
    public String bindCard(String userId, @NotNull IBankCard card, String clientIP) {
        var requestId = getRequestId();
        var SERVICE_CODE = "02";
        var channelUserId = getChannelUserId(userId);
        var bankName = card.getBankName();
        var cardType = "0" + (card.getCardType() + 1); // 01储蓄 02信用

        Map<String, Object> data = new TreeMap<>();
        data.put("userId", channelUserId);
        data.put("bankCardNo", card.getNumber());
        data.put("mobile", card.getPhoneNumber());
        data.put("bankName", bankName);
        data.put("bankCode", AnyPayConstant.getBankCode(bankName));
        data.put("productCategory", PRODUCT_CATEGORY);

        if (cardType.equals("02")) {
            data.put("valid", card.getValidation()); // MMYY
            data.put("cvn2", card.getCvv2());
        }

        AnyPayBaseRespModel res = httpClient.http("bindCard", requestId, data, SERVICE_CODE);
        var json = ChannelJSONUtil.toJson(res.getData());
        AnyPayBindCardResponse anyPayBindCardResponse = ChannelJSONUtil.parseJson(json, AnyPayBindCardResponse.class);
        var subAccountNo = anyPayBindCardResponse.getSubAccountNo();

        if (anyPayBindCardResponse.getStatus() == 1) {
            // 已经激活过了
            saveCardBoundRelationship(userId, card, channelUserId, subAccountNo);
            logger.info("bindCard success: CardAlreadyBoundException(REPEAT_CARD_BINDING)");
//            throw new CardAlreadyBoundException();
        }

        // 激活
        AnyPayActivateCardResponse anyPayActivateCardResponse =
                activateCard(channelUserId, subAccountNo);
//        if (anyPayActivateCardResponse.getStatus() == 0) {
//            // 未能激活成功
//            logger.fail(AnyPayConstant.ERROR_DEFAULT_CODE, "绑卡失败(bindCard fail): 未能激活");
//            return null; // @unreachable
//        }

        // 检查需不需要短信
        var verifyType = anyPayActivateCardResponse.getVerifyType();
        if (verifyType == AnyPayActivateCardResponse.NO_NEED_SMS) {
            // 直接激活成功
            saveCardBoundRelationship(userId, card, channelUserId, subAccountNo);
            logger.info("bindCard success: CardAlreadyBoundException(NO_NEED_SMS)");
//            var ignoreCardAlreadyBound = channelBaseService.sessionVarProvider.get("ignoreCardAlreadyBound");
//            if (ignoreCardAlreadyBound != null && !(boolean)ignoreCardAlreadyBound) {
//                throw new CardAlreadyBoundException();
//            }
            throw new CardAlreadyBoundException();
        }

        var serialNo = anyPayActivateCardResponse.getSerialNo(); // 绑卡token
        var uuid = UUID.randomUUID().toString();
        AnyPayBindCardPayload payload = AnyPayBindCardPayload.builder()
                .userId(userId)
                .card(card)
                .clientIP(clientIP)
                .requestId(requestId)
                .subAccountNo(subAccountNo)
                .serialNo(serialNo)
                .channelUserId(channelUserId)
                .build();
        channelBaseService.redis.boundValueOps(BINDCARD_SMS_PREFIX + uuid).set(
                ChannelJSONUtil.toJson(payload), ChannelConstant.ONE_DAY, TimeUnit.MILLISECONDS);

        logger.info("bindCard success");
        return uuid; // 返回绑卡token
    }

    private void saveCardBoundRelationship(String userId,
                                          @NotNull IBankCard card,
                                          String channelUserId,
                                          String subAccountNo
    ) {
        /*
            开始落库
            先判断交易卡是不是和结算卡同一张 防止重复插入同个cardId
         */
        AnyPayChannelBoundRelationshipTXRecord existsRecord = db.getRecordByCardId(card.getId());
        if (existsRecord != null) {
            existsRecord.setAgreementNo(subAccountNo);
            existsRecord.setBound(1); // 标志绑过卡
            db.updateByCardId(existsRecord);
        } else {
            AnyPayChannelBoundRelationshipTXRecord newRecord = AnyPayChannelBoundRelationshipTXRecord.builder()
                    .cardId(card.getId())
                    .bound(1)
                    .isBillingCard(0)
                    .cardType(card.getCardType())
                    .userId(userId)
                    .channelUserId(channelUserId)
                    .agreementNo(subAccountNo)
                    .build();
            db.insert(newRecord);
        }
    }

    public AnyPayActivateCardResponse activateCard(String channelUserId, String subAccountNo) {
        var requestId = getRequestId();
        var SERVICE_CODE = "03";
        Map<String, Object> data = new TreeMap<>();
        data.put("userId", channelUserId);
        data.put("subAccountNo", subAccountNo);
        AnyPayBaseRespModel res = httpClient.http("activateCard", requestId, data, SERVICE_CODE);
        var json = ChannelJSONUtil.toJson(res.getData());
        return ChannelJSONUtil.parseJson(json,
                AnyPayActivateCardResponse.class);
    }

    @Override
    public String tryBindCard(String userId, IBankCard card, String clientIP) {
        var location = channelBaseService.channelLocationService.getLocation();
        var province = location.getProvince();
        var city = location.getCity();
        var area = location.getArea();
        var payRate = "0.55";

        return tryBindCard(userId, card, clientIP, payRate, province, city, area);
    }

    @Override
    public String tryBindCard(String userId, @NotNull IBankCard card, String clientIP,
                              String payRate, String province, String city, String area) {
        boolean registered = this.checkIfRegistered(userId);
        int cardType = card.getCardType(); // 0 - 储蓄卡, 1 - 信用卡
        if (!registered) {
            if (cardType == 1) {
                // 未签约且是绑定信用卡 找一张储蓄卡来签约再绑卡
                var signUpCardTmp = channelBaseService.iUserBankCardProvider.getUserDebitCard(userId);
                // 这样拿到的卡是信息不完整的(比如说卡号打了🐴) 需要拿cardId换完整的卡
                var cardId = signUpCardTmp.getId();
                var signUpCard = channelBaseService.iUserBankCardProvider.getBankCard(cardId);
                this.userSignUp(userId, province, city, area, signUpCard, payRate);
                return this.bindCard(userId, card, clientIP);
            }
            // 未签约且是绑定储蓄卡直接签约
            this.userSignUp(userId, province, city, area, card, payRate);
        }
        // 无论储蓄卡还是消费卡都绑定一下
        return this.bindCard(userId, card, clientIP);
    }

    @Override
    public String bindCardConfirm(String smsCode, String userId, String smsUUID) {
        String valStr = channelBaseService.redis.boundValueOps(BINDCARD_SMS_PREFIX + smsUUID).get();
        if (valStr == null || valStr.isEmpty()) {
            logger.fail(AnyPayConstant.SMS_EXPIRED, "短信已过期!");
        }
        AnyPayBindCardPayload payload = ChannelJSONUtil.parseJson(valStr, AnyPayBindCardPayload.class);

        var requestId = getRequestId();
        var SERVICE_CODE = "04";
        var card = payload.getCard();
        var channelUserId = payload.getChannelUserId();
        var subAccountNo = payload.getSubAccountNo();
        Map<String, Object> data = new TreeMap<>();
        data.put("userId", userId);
        data.put("subAccountNo", subAccountNo);
        data.put("smsCode", smsCode);
        data.put("serialNo", payload.getSerialNo());
        AnyPayBaseRespModel res = httpClient.http("bindCardConfirm", requestId, data, SERVICE_CODE);
        var json = ChannelJSONUtil.toJson(res.getData());
        var resData = ChannelJSONUtil.parseJson(json, AnyPayBindCardConfirmResponse.class);
        if (resData.getStatus() == 0) {
            logger.fail(AnyPayConstant.ERROR_DEFAULT_CODE, "绑卡失败(bindCardConfirm fail): 未能激活");
            return null; // @unreachable
        }
        saveCardBoundRelationship(userId, card, channelUserId, subAccountNo);
        return card.getId();
    }

    @Override
    public String pay(String userId, @NotNull IBankCard payCard, IBankCard remitCard, @NotNull BigDecimal orderAmount,
                      BigDecimal userFeeAmount, String clientIP,
                      String province, String city, String area)
            throws PayChannelDeadException, TryingBindCardException {
        // 先更新结算卡
        var channelUserId = getChannelUserId(userId);
        updateBillingCard(userId, channelUserId, remitCard);

        var requestId = getRequestId();
        var SERVICE_CODE = "06";
        var spOrderId = UUID.randomUUID().toString().replaceAll("-", ""); // 订单号由这边生成
        var record = db.getRecordByCardId(payCard.getId());
        var orderCreateTimestamp = String.valueOf(System.currentTimeMillis());
        String callbackUrl = baseUrl + "channel/callback/" + CHANNEL_TYPE
                + "/payCallback/" + orderCreateTimestamp + "?info=" + requestId;
        var location = channelBaseService.channelLocationService.getLocation();

        Map<String, Object> data = new TreeMap<>();
        data.put("userId", channelUserId);
        data.put("subAccountNo", record.getAgreementNo());
        data.put("amount", orderAmount.multiply(BigDecimal.valueOf(100L)).longValue() + "");
        data.put("spOrderId", spOrderId);
        data.put("notifyUrl", callbackUrl);
        data.put("province", AnyPayConstant.getProvinceCode(location.getProvince()));
        data.put("city", AnyPayConstant.getCityCode(location.getCity()));
//        data.put("province", AnyPayConstant.getProvinceCode("广东省"));
//        data.put("city", AnyPayConstant.getCityCode("深圳市"));
        data.put("sourceIp", clientIP);
        data.put("payMode", "3"); // 短信验证
        data.put("goodsName", "牛排套餐");

        var res = httpClient.http("pay",
                requestId, data, SERVICE_CODE);
        var response = ChannelJSONUtil.parseJson(
                ChannelJSONUtil.toJson(res.getData()), AnyPayPayResponse.class);
        var WAITING = "waiting";
        var FAIL = "fail";
        var SETTLE_FAIL = "settleFail";
        var status = response.getStatus();
        var vipType = channelBaseService.iUserInfoProvider.getUserInfo(userId).getStringVipType();
        if (FAIL.equals(status) || SETTLE_FAIL.equals(status)) {
            ExecutionFlow failFlow = ExecutionFlow.builder()
                    .userId(userId)
                    .status(ExecutionFlow.FlowStatus.FAILED) // 在回调中改状态
                    .type(ExecutionFlow.Type.PAY)
                    .channelType(CHANNEL_TYPE)
                    .amount(orderAmount)
                    .extendInfo(ChannelJSONUtil.toJson(new HashMap<String, String>() {{
                        put("request", ChannelJSONUtil.toJson(data));
                        put("response", ChannelJSONUtil.toJson(res));
                    }}))
                    .cardId(payCard.getId())
                    .requestId(requestId)
                    .commission(userFeeAmount)
                    .remitFee(BigDecimal.ZERO)
                    .orderId(spOrderId)
                    .retryId("")
                    .vipType(vipType)
                    .createTime(new Date())
                    .failReason("")
                    .build();
            String failId = channelBaseService.flowService.add(failFlow);
            logger.fail(ChannelConstant.PAY_ERROR,
                    "支付失败(pay failed): spOrderId => " + spOrderId + "\n failId => " + failId, null, failId);
        }
        if (WAITING.equals(status)) {
            // 此时会发短信 先创建个订单
            ExecutionFlow executionFlow = ExecutionFlow.builder()
                    .userId(userId)
                    .status(ExecutionFlow.FlowStatus.INITIALIZED)
                    .type(ExecutionFlow.Type.PAY)
                    .channelType(CHANNEL_TYPE)
                    .amount(orderAmount)
                    .extendInfo(ChannelJSONUtil.toJson(new HashMap<String, String>() {{
                        put("request", ChannelJSONUtil.toJson(data));
                        put("response", ChannelJSONUtil.toJson(res));
                    }}))
                    .cardId(payCard.getId())
                    .requestId(requestId)
                    .commission(userFeeAmount)
                    .remitFee(BigDecimal.ZERO)
                    .orderId(spOrderId)
                    .retryId("")
                    .vipType(vipType)
                    .createTime(new Date())
                    .build();
            String executionFlowId = channelBaseService.flowService.add(executionFlow);
            logger.info("pay pre success with flow id：" + executionFlowId);

            // 告诉业务方还有短信
            TryPayPayload payload = TryPayPayload.builder()
                    .payCard(payCard)
                    .remitCard(remitCard)
                    .bindCardUUID(spOrderId) // 消费确认时 用订单号来对应短信token
                    .orderAmount(orderAmount)
                    .userFeeAmount(userFeeAmount)
                    .userId(userId)
                    .clientIP(clientIP)
                    .payRate("") // 重放消费时用不到
                    .province(province)
                    .city(city)
                    .area(area)
                    .payExecutionFlowId(executionFlowId)
                    .build();

            String msg = CHANNEL_NAME + " " + payCard.getBankName() + ChannelStringUtils.getMaskString(
                    payCard.getNumber(), 0, 3, 4) + "验证码";
            throw TryingBindCardException.builder()
                    .msg(msg)
                    .phoneNumber(payCard.getPhoneNumber())
                    .channelName(CHANNEL_NAME)
                    .channelType(CHANNEL_TYPE)
                    .tryPayPayloadJson(ChannelJSONUtil.toJson(payload))
                    .build();
        }
        return null;  // @unreachable
    }

    public AnyPayUpdateBillingCardResponse updateBillingCard(String userId, String channelUserId,
                                                             @NotNull IBankCard remitCard) {
        // 修改结算卡
        var requestId = getRequestId();
        var SERVICE_CODE = "16";
        var bankName = remitCard.getBankName();
        Map<String, Object> data = new TreeMap<>();
        data.put("userId", channelUserId);
        data.put("accountId", remitCard.getNumber());
        data.put("bankCode", AnyPayConstant.getBankCode(bankName));
        data.put("bankName", bankName);
        data.put("cnapsNo", "00000000000");
        data.put("bankProvince", AnyPayConstant.getProvinceCode(remitCard.getProvince()));
        data.put("bankCity", AnyPayConstant.getCityCode(remitCard.getCity()));
        data.put("productCategory", PRODUCT_CATEGORY);
        AnyPayBaseRespModel res = httpClient.http("updateBillingCard", requestId, data, SERVICE_CODE);
        var json = ChannelJSONUtil.toJson(res.getData());
        // 发完请求更新数据库里的结算卡
        var record = db.getBillingCardRecordByUserId(userId);
        record.setCardId(remitCard.getId());
        db.updateByUserId(record);
        return ChannelJSONUtil.parseJson(json, AnyPayUpdateBillingCardResponse.class);
    }

    @Override
    public String tryPay(String userId, IBankCard payCard, IBankCard remitCard, @NotNull BigDecimal orderAmount,
                         @NotNull BigDecimal userFeeAmount, String clientIP, String payRate,
                         String province, String city, String area)
            throws TryingBindCardException, PayChannelDeadException {
        logParams("tryPay", userId, ChannelJSONUtil.toJson(payCard),  ChannelJSONUtil.toJson(remitCard),
                orderAmount.toString(), userFeeAmount.toString(), clientIP, payRate, province, city, area);
        // 业务方的费率是以%为单位的 如5%就是5 anypay要求以1为单位 所以这里要除以100
        String _payRate = BigDecimal.valueOf(Double.parseDouble(payRate)).divide(BigDecimal.valueOf(100L)).toString();

        // 信用卡要绑定 储蓄卡不用
        if (!this.checkIfBoundInner(payCard)) {
            try {
                try {
                    String bindCardUUID = this.tryBindCard(userId, payCard, clientIP, payRate, province, city, area);
                    TryPayPayload payload = TryPayPayload.builder()
                            .payCard(payCard)
                            .remitCard(remitCard)
                            .bindCardUUID(bindCardUUID)
                            .orderAmount(orderAmount)
                            .userFeeAmount(userFeeAmount)
                            .userId(userId)
                            .clientIP(clientIP)
                            .payRate(payRate)
                            .province(province)
                            .city(city)
                            .area(area)
                            .payExecutionFlowId(null)
                            .build();
                    String msg = CHANNEL_NAME + " " + payCard.getBankName() + ChannelStringUtils.getMaskString(
                            payCard.getNumber(), 0, 3, 4) + "验证码";
                    throw TryingBindCardException.builder()
                            .msg(msg)
                            .phoneNumber(payCard.getPhoneNumber())
                            .channelName(CHANNEL_NAME)
                            .channelType(CHANNEL_TYPE)
                            .tryPayPayloadJson(ChannelJSONUtil.toJson(payload))
                            .build();
                } catch (CardAlreadyBoundException e) {
                    var ignoreCardAlreadyBound = channelBaseService.sessionVarProvider.get("ignoreCardAlreadyBound");
                    if (ignoreCardAlreadyBound != null && (boolean) ignoreCardAlreadyBound) {
                        logger.info("安派通道: CardAlreadyBoundException Skipped");
                    } else {
                        throw new CardAlreadyBoundException();
                    }
                }
            } catch (PayChannelException e) {
                logger.errorInfo(e.getMessage(), e);
                throw PayChannelDeadException.builder()
                        .userFeeAmount(userFeeAmount)
                        .payRate(payRate)
                        .clientIP(clientIP)
                        .channelName(CHANNEL_NAME)
                        .remitCard(remitCard)
                        .payCard(payCard)
                        .amount(orderAmount)
                        .build();
            }
        }

        try {
            this.updatePayRate(userId, _payRate);
            return this.pay(
                    userId,
                    payCard, remitCard,
                    orderAmount, userFeeAmount,
                    clientIP,
                    province, city, area
            );
        } catch (PayChannelDeadException e) {
            logger.errorInfo(e.getMessage(), e);
            e.setPayRate(payRate); // 补全pay方法里没能写入的信息
            throw e; // 抛出给dispatcher处理
        }
    }

    @Override
    public String verifyPay(String userId, String smsCode, TryPayPayload payload)
            throws TryingBindCardException, PayChannelDeadException {
        logParams("verifyPay", userId, smsCode,  ChannelJSONUtil.toJson(payload));
        var executionFlowId = payload.getPayExecutionFlowId();

        String uuid = payload.getBindCardUUID();
        if (executionFlowId == null) {
            // 说明是因为绑卡短信重放 此时uuid是绑卡短信token的uuid 可换出绑卡序列号去绑卡确认
            this.bindCardConfirm(smsCode, userId, uuid); // 绑卡成功
            // 接下来 重放消费
            return this.tryPay(
                    userId,
                    payload.getPayCard(), payload.getRemitCard(),
                    payload.getOrderAmount(), payload.getUserFeeAmount(),
                    payload.getClientIP(), payload.getPayRate(),
                    payload.getProvince(), payload.getCity(), payload.getArea()
            );
        } else {
            // 说明是因为支付短信重放 此时uuid是订单号 这个是支付确认的token
            var flow = channelBaseService.flowService.getFlowPrecisely(executionFlowId, new Date()); // 一定是今天的支付订单
            if (flow == null) {
                logger.info("支付确认失败(找不到对应流水) cannot find flow when verifyPay");
                logger.fail(AnyPayConstant.ERROR_PARAMETER_WRONG, "支付确认失败(找不到对应流水)");
            }
            assert flow != null;
            var payCard = payload.getPayCard();
            var payCardId = payCard.getId();
            var record = db.getRecordByCardId(payCardId);
            var subAccountNo = record.getAgreementNo();
            confirmPay(userId, subAccountNo, uuid, smsCode, flow);

            ExecutionFlow remitFlow = ExecutionFlow.builder()
                    .userId(userId)
                    .type(ExecutionFlow.Type.REMIT)
                    .status(ExecutionFlow.FlowStatus.INITIALIZED)
                    .channelType(CHANNEL_TYPE)
                    .amount(payload.getOrderAmount())
                    .extendInfo("此订单为自动代付订单")
                    .cardId(payload.getRemitCard().getId())
                    .requestId(flow.getRequestId() + "_remit")
                    .commission(BigDecimal.ZERO)
                    .remitFee(payload.getUserFeeAmount())
                    .orderId(flow.getOrderId() + "_remit")
                    .retryId("")
                    .vipType(channelBaseService.iUserInfoProvider.getUserInfo(userId).getStringVipType())
                    .createTime(new Date())
                    .build();
            String remitFlowId = channelBaseService.flowService.add(remitFlow);
            logger.info("remit订单创建成功 流水id：" + remitFlowId);

            return executionFlowId;
        }
    }

    // 支付短信确认
    public void confirmPay(String userId, String subAccountNo, String spOrderId, String smsCode, ExecutionFlow flow) {
        var requestId = getRequestId();
        var SERVICE_CODE = "11";
        Map<String, Object> data = new TreeMap<>();
        data.put("userId", getChannelUserId(userId));
        data.put("subAccountNo", subAccountNo);
        data.put("spOrderId", spOrderId);
        data.put("smsCode", smsCode);

        try {
            AnyPayBaseRespModel res = httpClient.http("confirmPay", requestId, data, SERVICE_CODE);
            var json = ChannelJSONUtil.toJson(res.getData());
            var response = ChannelJSONUtil.parseJson(json, AnyPayPayConfirmResponse.class);
            var FAIL = "settleFail";
            var status = response.getStatus();
            if (FAIL.equals(status)) {
                flow.setStatus(ExecutionFlow.FlowStatus.FAILED);
                channelBaseService.flowService.fail(flow);
                String failId = flow.getId();
                logger.fail(ChannelConstant.PAY_ERROR,
                        "支付失败(pay failed) 请求失败: spOrderId => " + spOrderId + "\n failId => " + failId, null, failId);
                channelBaseService.iNotifyProvider.orderStatusNotify(flow,
                        INotifyProvider.OrderStatus.FAILED, null);
            }
        } catch (AnyPayException e) {
            logger.info("安派confirmPay瞎鸡儿返回resultCode非0（fuck anypay）: " + e.toString() );
            var nestedException = e.getNestedException();
            if (nestedException instanceof AnyPayException) {
                var model = ((AnyPayException) nestedException).getRsp();
                var code = model != null ?  model.getErrorCode() : e.getMessage();
                var nestedReason = model != null ? model.getErrorDesc() : "";
                var reason = ChannelStringUtils.stringOr(nestedReason, e.getMessage(), "其他错误");
                flow.setStatus(ExecutionFlow.FlowStatus.FAILED);
                channelBaseService.flowService.fail(flow);
                String failId = flow.getId();
                channelBaseService.iNotifyProvider.orderStatusNotify(flow,
                        INotifyProvider.OrderStatus.FAILED, null);
                if ("U011".equals(code)) { // 余额不足
                    logger.fail(ChannelConstant.PAY_ERROR,
                            "支付失败(confirmPay failed) 余额不足: spOrderId => " + spOrderId + "\n failId => " + failId, null, failId);
                }
                if ("F012".equals(code)) { // 重复下单
                    logger.fail(ChannelConstant.PAY_ERROR,
                            "支付失败(confirmPay failed) 订单已存在支付记录，请重新下单: spOrderId => " + spOrderId + "\n failId => " + failId, null, failId);
                }
                logger.fail(ChannelConstant.PAY_ERROR,
                        "支付失败(confirmPay failed) " + reason + " : spOrderId => " + spOrderId + "\n failId => " + failId, null, failId);
            } else {
                logger.fail(ChannelConstant.PAY_ERROR, "支付失败(confirmPay failed) ", e);
            }
        }
    }

    @Override
    public String remit(String userId, IBankCard remitCard, @NotNull IBankCard payCard, @NotNull BigDecimal amount,
                        @NotNull BigDecimal userFeeAmount, boolean active,
                        TryBindCardExtendInfo tryBindCardExtendInfo) throws RemitCardUnboundException {
        logParams("fakeRemit", userId,
                ChannelJSONUtil.toJson(remitCard),  ChannelJSONUtil.toJson(payCard),
                amount.toString(), userFeeAmount.toString(), active + "",
                ChannelJSONUtil.toJson(tryBindCardExtendInfo));
        return null;
    }

    @Override
    public boolean checkIfBound(@NotNull IBankCard card, boolean isRemitCard) {
        // 一定有短信过程 因此告诉业务两张卡都没绑过 业务方不用发
        return false;
    }

    /**
     * 真正判断卡绑定没的接口
     * @param card 卡
     * @return 是否绑定
     */
    private boolean checkIfBoundInner(@NotNull IBankCard card) {
        // 储蓄卡不用绑定
        if (card.getCardType() == 0) {
            return true;
        }
        var record = db.getRecordByCardId(card.getId());
        var hasBound = !(record == null || record.getBound() == 0 || TextUtils.isEmpty(record.getAgreementNo()));
        if (!hasBound) {
            return false;
        }
        // 已经绑定过了 检查是否有未激活通道
        var isExistInactive = querySubAccInfo(record.getChannelUserId(), record.getAgreementNo()).getIsExistInactive();
        return "0".equals(isExistInactive);
    }

    @Override
    public void handleCallback(String callBackAction, HttpServletRequest request,
                               HttpServletResponse response, String orderCreateTimestamp, String info) {
        var createTime = new Date(Long.parseLong(orderCreateTimestamp));
        var WAITING = "waiting"; // pay - remit -
        var FAIL = "fail"; // pay × remit -

        var SUCCESS = "success"; // 消费成功 pay √ remit -
        var SETTLE_WAI = "settleWai"; // 结算中 pay √ remit -

        var SETTLE_FAIL = "settleFail"; // 结算失败 结算中 pay √ remit ×
        var SETTLE_SUC = "settleSuc"; // 结算成功 结算中 pay √ remit √

        if (AnyPayConstant.CALLBACK_PAY.equals(callBackAction)) {
            logger.info("收到支付回调(handling pay callback), requestId => " + info
                    + " orderCreateTimestamp => " + orderCreateTimestamp); // requestId放在了qs里

            AnyPayBaseRespModel model = httpClient.getResp(getRequestBody(request), true);
            var dataJson = ChannelJSONUtil.toJson(model.getData());
            logger.info("callback data json: " + dataJson);

            var res = ChannelJSONUtil.parseJson(dataJson, AnyPayTxCallbackResponse.class);

            var orderId = res.getSpOrderId();
            var orderStatus = res.getStatus();

            logger.info("支付结果(pay callback result): orderId => " +
                    orderId + " orderStatus => " + orderStatus);

            if (WAITING.equals(orderStatus)) {
                endCallBack("pay", response);
                return;
            }

            // 查询原支付流水
            ExecutionFlow payFlow = ExecutionFlow.builder()
                    .requestId(info)
                    .createTime(createTime)
                    .build();
            // 查询原代付流水
            ExecutionFlow remitFlow = ExecutionFlow.builder()
                    .requestId(info + "_remit")
                    .createTime(createTime)
                    .build();

            if (FAIL.equals(orderStatus)) {
                logger.info("orderId: " + orderId + "支付失败");
                // 对失败状态和extendInfo落库 并通知业务
                var reason = res.getRespMsg();
                // 更新extendInfo
                var payRequestCtx = channelBaseService.flowService.getByRequestIdAndCreateTime(payFlow).getExtendInfo();
                var remitRequestCtx = channelBaseService.flowService.getByRequestIdAndCreateTime(remitFlow).getExtendInfo();
                var finalReason = reason; // 内部类访问 需要final
                var newPayInfo = ChannelJSONUtil.toJson(new HashMap<String, String>() {{
                    put("reason", finalReason);
                    put("requestCtx", payRequestCtx);
                }});
                var newRemitInfo = ChannelJSONUtil.toJson(new HashMap<String, String>() {{
                    put("reason", finalReason);
                    put("requestCtx", remitRequestCtx);
                }});
                payFlow.setFailReason(finalReason);
                remitFlow.setFailReason(finalReason);
                ExecutionFlow failPayFlow = channelBaseService.flowService.fail(payFlow, newPayInfo);
                ExecutionFlow failRemitFlow = channelBaseService.flowService.fail(remitFlow, newRemitInfo);
                if (failPayFlow == null || failRemitFlow == null) {
                    logger.info("[!!!!!!!!!!!!!!!!]找不到回调对应的订单或者订单已经被处理过"
                            + (failPayFlow == null) + "," + (failRemitFlow == null));
                    endCallBack("pay", response);
                    return;
                }
                channelBaseService.iNotifyProvider.orderStatusNotify(failPayFlow, INotifyProvider.OrderStatus.FAILED, null);
            }

            if (SUCCESS.equals(orderStatus) || SETTLE_WAI.equals(orderStatus) || SETTLE_SUC.equals(orderStatus)) {
                logger.info("orderId: " + orderId + "支付成功，代付成功");
                ExecutionFlow successFlow = channelBaseService.flowService.success(payFlow);
                ExecutionFlow successRemitFlow = channelBaseService.flowService.success(remitFlow);
                if (successFlow == null) {
                    logger.info("[!!!!!!!!!!!!!!!!]找不到回调对应的订单或者订单已经被处理过");
                } else {
                    channelBaseService.iNotifyProvider.orderStatusNotify(successFlow, INotifyProvider.OrderStatus.SUCCEED, successRemitFlow);
                }
                if (successRemitFlow == null) {
                    logger.info("[!!!!!!!!!!!!!!!!]找不到回调对应的订单或者订单已经被处理过");
                } else {
                    channelBaseService.iNotifyProvider.orderStatusNotify(successRemitFlow, INotifyProvider.OrderStatus.SUCCEED, null);
                }
            }

            endCallBack("pay", response);
        }
    }

    private void endCallBack(String type, @NotNull HttpServletResponse response) {
        try {
            response.getWriter().write(ChannelConstant.SUCCESS);
        } catch (IOException e) {
            logger.info(type + "回调异常：" + e.toString());
        }
    }

    private String getRequestBody(@NotNull HttpServletRequest request) {
        try {
            String json = IOUtils.toString(request.getReader());
            logger.info("callback body json: " + json);
            return json;
        } catch (IOException e) {
            logger.info("回调异常：" + e.toString());
        }
        return "";
    }

    public AnyPayQuerySubAccInfoResponse querySubAccInfo(String channelUserId, String subAccountNo) {
        var requestId = getRequestId();
        var SERVICE_CODE = "10";
        Map<String, Object> data = new TreeMap<>();
        data.put("userId", channelUserId);
        data.put("subAccountNo", subAccountNo);
        var res = httpClient.http("querySubAccInfo",
                requestId, data, SERVICE_CODE);
        return ChannelJSONUtil.parseJson(
                ChannelJSONUtil.toJson(res.getData()), AnyPayQuerySubAccInfoResponse.class);
    }

    @Override
    public ArrayList<ChannelBalance> getBalance(String userId) {
        ArrayList<ChannelBalance> list = new ArrayList<>();
        boolean registered = this.checkIfRegistered(userId);
        if (!registered) {
            // 未签约
            logger.info("未签约通道，余额-1");
            list.add(ChannelBalance.builder()
                    .channelName(CHANNEL_NAME)
                    .channelType(CHANNEL_TYPE)
                    .balance(BigDecimal.valueOf(-1))
                    .build());
            return list;
        }

        var records = db.getAllRecordByUserId(userId);
        records.forEach(record -> {
            var subAccountNo = record.getAgreementNo();
            if (!TextUtils.isEmpty(subAccountNo)) {
                var balance = querySubAccInfo(getChannelUserId(userId), subAccountNo).getBalanceAmt();
                list.add(ChannelBalance.builder()
                        .channelName(CHANNEL_NAME)
                        .channelType(CHANNEL_TYPE + "$" + record.getCardId()) // 特殊逻辑 拿channelType当作extendInfo 记录这张卡的信息
                        .balance(BigDecimal.valueOf(balance).divide(BigDecimal.valueOf(100)))
                        .build());
            }
        });
        // 理论上余额应该全是0 因为是全自动代付 不会有余额
        return list;
    }
}
